/**
 * Copyright (c) 2011-2025 libbitcoin developers (see AUTHORS)
 *
 * This file is part of libbitcoin.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <bitcoin/system/chain/point.hpp>

#include <utility>
#include <bitcoin/system/chain/enums/magic_numbers.hpp>
#include <bitcoin/system/define.hpp>
#include <bitcoin/system/math/math.hpp>
#include <bitcoin/system/stream/stream.hpp>

namespace libbitcoin {
namespace system {
namespace chain {

// This sentinel is serialized and defined by consensus, not implementation.
const uint32_t point::null_index = no_previous_output;

// Constructors.
// ----------------------------------------------------------------------------

// Invalid default used in signature hashing.
point::point() NOEXCEPT
  : point(null_hash, point::null_index, false)
{
}

point::point(hash_digest&& hash, uint32_t index) NOEXCEPT
  : point(std::move(hash), index, true)
{
}

point::point(const hash_digest& hash, uint32_t index) NOEXCEPT
  : point(hash, index, true)
{
}

point::point(const data_slice& data) NOEXCEPT
  : point(stream::in::fast(data))
{
}

// protected
point::point(stream::in::fast&& stream) NOEXCEPT
  : point(read::bytes::fast(stream))
{
}

point::point(stream::in::fast& stream) NOEXCEPT
  : point(read::bytes::fast(stream))
{
}
point::point(std::istream& stream) NOEXCEPT
  : point(read::bytes::istream(stream))
{
}

// protected
point::point(reader&& source) NOEXCEPT
  : point(source)
{
}

point::point(reader& source) NOEXCEPT
{
    assign_data(source);
}

// protected
point::point(hash_digest&& hash, uint32_t index, bool valid) NOEXCEPT
  : hash_(std::move(hash)), index_(index), valid_(valid)
{
}

// protected
point::point(const hash_digest& hash, uint32_t index, bool valid) NOEXCEPT
  : hash_(hash), index_(index), valid_(valid)
{
}

// Operators.
// ----------------------------------------------------------------------------

bool point::operator==(const point& other) const NOEXCEPT
{
    return (hash_ == other.hash_)
        && (index_ == other.index_);
}

bool point::operator!=(const point& other) const NOEXCEPT
{
    return !(*this == other);
}

bool operator<(const point& left, const point& right) NOEXCEPT
{
    // Arbitrary compare, for uniqueness sorting.
    return left.index() == right.index() ?
        left.hash() < right.hash() :
        left.index() < right.index();
}

// Constant reference optimizers.

bool operator<(const point_cref& left, const point_cref& right) NOEXCEPT
{
    return left.get() < right.get();
}

bool operator==(const point_cref& left, const point_cref& right) NOEXCEPT
{
    return left.get() == right.get();
}

bool operator!=(const point_cref& left, const point_cref& right) NOEXCEPT
{
    return !(left == right);
}


// Deserialization.
// ----------------------------------------------------------------------------

// private
void point::assign_data(reader& source) NOEXCEPT
{
    source.read_bytes(hash_.data(), hash_size);
    index_ = source.read_4_bytes_little_endian();
    valid_ = source;
}

// Serialization.
// ----------------------------------------------------------------------------

data_chunk point::to_data() const NOEXCEPT
{
    data_chunk data(serialized_size());
    stream::out::fast ostream(data);
    write::bytes::fast out(ostream);
    to_data(out);
    return data;
}

void point::to_data(std::ostream& stream) const NOEXCEPT
{
    write::bytes::ostream out(stream);
    to_data(out);
}

void point::to_data(writer& sink) const NOEXCEPT
{
    sink.write_bytes(hash_);
    sink.write_4_bytes_little_endian(index_);
}

// Properties.
// ----------------------------------------------------------------------------

bool point::is_valid() const NOEXCEPT
{
    return valid_;
}

const hash_digest& point::hash() const NOEXCEPT
{
    return hash_;
}

uint32_t point::index() const NOEXCEPT
{
    return index_;
}

// Validation.
// ----------------------------------------------------------------------------

bool point::is_null() const NOEXCEPT
{
    return (index_ == null_index) && (hash_ == null_hash);
}

// JSON value convertors.
// ----------------------------------------------------------------------------

DEFINE_JSON_TO_TAG(point)
{
    return
    {
        decode_hash<hash_size>(value.at("hash").as_string()),
        value.at("index").to_number<uint32_t>()
    };
}

DEFINE_JSON_FROM_TAG(point)
{
    value =
    {
        { "hash", encode_hash(instance.hash()) },
        { "index", instance.index() }
    };
}

DEFINE_JSON_TO_TAG(point::cptr)
{
    return to_shared(tag_invoke(to_tag<point>{}, value));
}

DEFINE_JSON_FROM_TAG(point::cptr)
{
    tag_invoke(from_tag{}, value, *instance);
}

} // namespace chain
} // namespace system
} // namespace libbitcoin
